package com.edu.dk.cas.client.session;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.session.HashMapBackedSessionMappingStorage;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;

public class SingleSignOutClusterHandler {
	private final Log log = LogFactory.getLog(getClass());
	private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage();
	private String artifactParameterName = "ticket";
	private String logoutParameterName = "logoutRequest";
	private String logoutClusterNodePatameterName = "logoutClusterNodeRequest";
	private String[] clusterNodes = null;

	public void setSessionMappingStorage(SessionMappingStorage storage) {
		this.sessionMappingStorage = storage;
	}

	public SessionMappingStorage getSessionMappingStorage() {
		return this.sessionMappingStorage;
	}

	public void setArtifactParameterName(String name) {
		this.artifactParameterName = name;
	}

	public void setLogoutParameterName(String name) {
		this.logoutParameterName = name;
	}

	public void setLogoutClusterNodePatameterName(String logoutClusterNodePatameterName) {
		this.logoutClusterNodePatameterName = logoutClusterNodePatameterName;
	}

	public void setClusterNodes(String nodes) {
		if (nodes != null) {
			clusterNodes = nodes.trim().split(",");
		}
	}

	public void init() {
		CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
		CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
		CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannote be null.");
	}

	public boolean isTokenRequest(HttpServletRequest request) {
		return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName));
	}

	public boolean isLogoutRequest(HttpServletRequest request) {
		return ("POST".equals(request.getMethod())) && (!isMultipartRequest(request))
				&& (CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)));
	}

	public void recordSession(HttpServletRequest request) {
		HttpSession session = request.getSession(true);

		String token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
		if (this.log.isDebugEnabled()) {
			this.log.debug("Recording session for token " + token);
		}
		try {
			this.sessionMappingStorage.removeBySessionById(session.getId());
		} catch (Exception e) {
		}
		this.sessionMappingStorage.addSessionById(token, session);
	}

	public void destroySession(HttpServletRequest request) {
		String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName);
		if (this.log.isTraceEnabled()) {
			this.log.trace("Logout request:\n" + logoutMessage);
		}
		String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
		if (CommonUtils.isNotBlank(token)) {
			HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
			if (session != null) {
				String sessionID = session.getId();
				if (this.log.isDebugEnabled()) {
					this.log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]");
				}
				try {
					session.invalidate();
				} catch (IllegalStateException e) {
					this.log.debug("Error invalidating session.", e);
				}
			} else {
				// 会话不在本机节点上，向其他节点发出注销命令
				destorySessionOfClusterNodes(token);
			}
		}
	}

	/**
	 * 向其他节点发送注销命令
	 * 
	 * @param token
	 */
	private void destorySessionOfClusterNodes(String token) {
		if (clusterNodes != null) {
			for (String node : clusterNodes) {
				log.info("Send to cluster node to logout: [" + node + "]");
				call(node.trim(), token);
			}
		}
	}

	private Boolean call(String url, String token) {
		HttpURLConnection connection = null;
		BufferedReader in = null;
		try {
			if (log.isDebugEnabled()) {
				log.debug("Attempting to access " + url);
			}
			final URL logoutUrl = new URL(url);
			final String output = this.logoutClusterNodePatameterName + "=" + URLEncoder.encode(token, "UTF-8");

			connection = (HttpURLConnection) logoutUrl.openConnection();
			connection.setDoInput(true);
			connection.setDoOutput(true);
			connection.setRequestMethod("POST");
			connection.setReadTimeout(5000);
			connection.setConnectTimeout(5000);
			connection.setRequestProperty("Content-Length", Integer.toString(output.getBytes().length));
			connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			final DataOutputStream printout = new DataOutputStream(connection.getOutputStream());
			printout.writeBytes(output);
			printout.flush();
			printout.close();

			in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

			while (in.readLine() != null) {
				// nothing to do
			}

			if (log.isDebugEnabled()) {
				log.debug("Finished sending message to" + url);
			}
			return true;
		} catch (final SocketTimeoutException e) {
			log.warn("Socket Timeout Detected while attempting to send message to [" + url + "].");
			return false;
		} catch (final Exception e) {
			log.warn("Error Sending message to url endpoint [" + url + "].  Error is [" + e.getMessage() + "]");
			return false;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (final IOException e) {
					// can't do anything
				}
			}
			if (connection != null) {
				connection.disconnect();
			}
		}
	}

	private boolean isMultipartRequest(HttpServletRequest request) {
		return (request.getContentType() != null) && (request.getContentType().toLowerCase().startsWith("multipart"));
	}

	/**
	 * 是否是由集群节点发送过来的注销请求
	 * 
	 * @param request
	 * @return
	 */
	public boolean isLogoutRequestFromClusterNode(HttpServletRequest request) {
		return ("POST".equals(request.getMethod())) && (!isMultipartRequest(request))
				&& (CommonUtils.isNotBlank(request.getParameter(this.logoutClusterNodePatameterName)));
	}

	/**
	 * 处理由集群节点发过来的注销请求
	 * 
	 * @param request
	 */
	public void destroySessionFromClusterNode(HttpServletRequest request) {
		// 获取token
		String token = request.getParameter(this.logoutClusterNodePatameterName);
		if (CommonUtils.isNotBlank(token)) {
			// 根据token获取session
			HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
			if (session != null) {
				// 说明找对了集群节点，那么执行注销
				String sessionID = session.getId();
				if (this.log.isDebugEnabled()) {
					this.log.debug("Invalidating session [" + sessionID + "] for token [" + token + "]");
				}
				try {
					session.invalidate();
				} catch (IllegalStateException e) {
					this.log.debug("Error invalidating session.", e);
				}
			}
		}

	}

}
